import { type Table } from '../types/explore';
import { DimensionType, FieldType, MetricType } from '../types/field';
import { expandFieldsWithSets } from './fieldSetExpander';

const createMockTable = (
    name: string,
    dimensions: string[],
    metrics: string[],
    sets?: Record<string, { fields: string[]; description?: string }>,
): Table => ({
    name,
    label: name,
    database: 'test_db',
    schema: 'test_schema',
    sqlTable: name,
    description: `${name} table`,
    dimensions: dimensions.reduce(
        (acc, dim) => ({
            ...acc,
            [dim]: {
                fieldType: FieldType.DIMENSION,
                type: DimensionType.STRING,
                name: dim,
                label: dim,
                table: name,
                tableLabel: name,
                sql: `\${TABLE}.${dim}`,
                hidden: false,
                index: 0,
            },
        }),
        {},
    ),
    metrics: metrics.reduce(
        (acc, metric) => ({
            ...acc,
            [metric]: {
                fieldType: FieldType.METRIC,
                type: MetricType.COUNT,
                name: metric,
                label: metric,
                table: name,
                tableLabel: name,
                sql: `COUNT(\${${metric}})`,
                hidden: false,
                index: 0,
                isAutoGenerated: false,
            },
        }),
        {},
    ),
    lineageGraph: {},
    sets,
});

describe('expandFieldsWithSets', () => {
    describe('Basic Expansion', () => {
        it('should expand a simple set reference', () => {
            const table = createMockTable(
                'orders',
                ['order_id', 'customer_id', 'total'],
                ['count'],
                {
                    basic_fields: {
                        fields: ['order_id', 'customer_id'],
                    },
                },
            );

            const result = expandFieldsWithSets(['basic_fields*'], table);

            expect(result).toEqual(['order_id', 'customer_id']);
        });

        it('should pass through regular field references unchanged', () => {
            const table = createMockTable(
                'orders',
                ['order_id', 'customer_id'],
                [],
            );

            const result = expandFieldsWithSets(
                ['order_id', 'customer_id'],
                table,
            );

            expect(result).toEqual(['order_id', 'customer_id']);
        });

        it('should mix set references and regular fields', () => {
            const table = createMockTable(
                'orders',
                ['order_id', 'customer_id', 'created_at', 'total'],
                [],
                {
                    id_fields: {
                        fields: ['order_id', 'customer_id'],
                    },
                },
            );

            const result = expandFieldsWithSets(
                ['id_fields*', 'created_at', 'total'],
                table,
            );

            expect(result).toEqual([
                'order_id',
                'customer_id',
                'created_at',
                'total',
            ]);
        });
    });

    describe('Exclusions', () => {
        it('should exclude fields with - prefix', () => {
            const table = createMockTable(
                'orders',
                ['order_id', 'customer_id', 'total', 'tax'],
                [],
                {
                    all_fields: {
                        fields: ['order_id', 'customer_id', 'total', 'tax'],
                    },
                },
            );

            const result = expandFieldsWithSets(['all_fields*', '-tax'], table);

            expect(result).toEqual(['order_id', 'customer_id', 'total']);
        });

        it('should handle multiple exclusions', () => {
            const table = createMockTable(
                'orders',
                ['a', 'b', 'c', 'd', 'e'],
                [],
                {
                    all_fields: {
                        fields: ['a', 'b', 'c', 'd', 'e'],
                    },
                },
            );

            const result = expandFieldsWithSets(
                ['all_fields*', '-b', '-d'],
                table,
            );

            expect(result).toEqual(['a', 'c', 'e']);
        });

        it('handles nested exclusions', () => {
            const table = createMockTable(
                'orders',
                ['a', 'b', 'c', 'd', 'e'],
                [],
                {
                    base_fields: {
                        fields: ['a', 'b', 'c'],
                    },
                    all_fields: {
                        fields: ['base_fields*', '-c'],
                    },
                },
            );

            const result = expandFieldsWithSets(['all_fields*'], table);

            expect(result).toEqual(['a', 'b']);
        });

        it('should preserve order when excluding', () => {
            const table = createMockTable(
                'orders',
                ['created_at', 'order_id', 'customer_id', 'updated_at'],
                [],
                {
                    middle_fields: {
                        fields: ['order_id', 'customer_id'],
                    },
                },
            );

            const result = expandFieldsWithSets(
                ['created_at', 'middle_fields*', 'updated_at', '-customer_id'],
                table,
            );

            expect(result).toEqual(['created_at', 'order_id', 'updated_at']);
        });
    });

    describe('Nested Sets', () => {
        it('should expand nested set references (one level)', () => {
            const table = createMockTable(
                'orders',
                ['order_id', 'customer_id', 'total', 'tax'],
                [],
                {
                    id_fields: {
                        fields: ['order_id', 'customer_id'],
                    },
                    money_fields: {
                        fields: ['total', 'tax'],
                    },
                    all_fields: {
                        fields: ['id_fields*', 'money_fields*'],
                    },
                },
            );

            const result = expandFieldsWithSets(['all_fields*'], table);

            expect(result).toEqual(['order_id', 'customer_id', 'total', 'tax']);
        });

        it('should throw error for sets with more than one level of nesting', () => {
            const table = createMockTable('orders', ['a', 'b', 'c'], [], {
                level1: { fields: ['a'] },
                level2: { fields: ['level1*', 'b'] },
                level3: { fields: ['level2*', 'c'] },
            });

            // level3 contains level2*, and level2 contains level1*
            // This violates one-level nesting rule
            expect(() => expandFieldsWithSets(['level3*'], table)).toThrow(
                /only one level of nesting is allowed/,
            );
        });
    });

    describe('Error Handling', () => {
        it('should throw error for non-existent set', () => {
            const table = createMockTable('orders', ['order_id'], []);

            expect(() =>
                expandFieldsWithSets(['nonexistent_set*'], table),
            ).toThrow(/Set "nonexistent_set" not found/);
        });

        it('should detect direct circular references', () => {
            const table = createMockTable('orders', ['field1'], [], {
                set_a: { fields: ['set_b*'] },
                set_b: { fields: ['set_a*'] },
            });

            expect(() => expandFieldsWithSets(['set_a*'], table)).toThrow(
                /Circular reference detected/,
            );
        });
    });

    describe('Deduplication', () => {
        it('should deduplicate fields from multiple sets', () => {
            const table = createMockTable(
                'orders',
                ['order_id', 'customer_id', 'total'],
                [],
                {
                    set1: { fields: ['order_id', 'customer_id'] },
                    set2: { fields: ['customer_id', 'total'] },
                },
            );

            const result = expandFieldsWithSets(['set1*', 'set2*'], table);

            expect(result).toEqual(['order_id', 'customer_id', 'total']);
        });

        it('should not duplicate explicitly listed fields', () => {
            const table = createMockTable(
                'orders',
                ['order_id', 'customer_id'],
                [],
                {
                    id_fields: { fields: ['order_id', 'customer_id'] },
                },
            );

            const result = expandFieldsWithSets(
                ['order_id', 'id_fields*'],
                table,
            );

            expect(result).toEqual(['order_id', 'customer_id']);
        });
    });

    describe('Empty Sets', () => {
        it('should handle empty field list', () => {
            const table = createMockTable('orders', ['order_id'], []);

            const result = expandFieldsWithSets([], table);

            expect(result).toEqual([]);
        });

        it('should handle only exclusions', () => {
            const table = createMockTable('orders', ['order_id'], []);

            const result = expandFieldsWithSets(['-order_id'], table);

            expect(result).toEqual([]);
        });
    });
});
